package org.geoscrape;

import java.io.Serializable;

/**
 * Implements a latitude or longitude in the WGS84 datum. Sign indicates
 * east/west and north south, with positive being North-east.
 * 
 */
public class Coordinate implements Serializable
{
	private static final long serialVersionUID = -3593731490210324085L;
	private int degree;
	private int minutes;
	private int fractionalMinutes;
	private boolean negative;

	public Coordinate()
	{
	}

	/**
	 * Set the coordinate from the degree min.fracMin format, e.g. N 14 14.345 E
	 * 009 10.345
	 * 
	 * Positive indicates North and East, negative South and West.
	 * 
	 * @param deg
	 *            the degrees.
	 * @param min
	 *            the whole minutes.
	 * @param fracMin
	 *            the fractional minutes, in thousands of a minute.
	 * 
	 */
	public Coordinate(int deg, int min, int fracMin)
	{
		setDegree(deg);
		setMinutes(min);
		setFractionalMinutes(fracMin);
	}

	public Coordinate(Coordinate other)
	{
		setDegree(other.getDegree());
		setMinutes(other.getMinutes());
		setFractionalMinutes(other.getFractionalMinutes());
		setNegative(other.isNegative());
	}

	public Coordinate(double degree)
	{
		if (degree >= 0)
		{
			int deg = (int) Math.floor(degree);
			double remainder = degree - deg;
			double minutes = remainder * 60;
			int min = (int) Math.floor(minutes);
			int fracMin = (int) Math.floor((minutes - min) * 1000);
			setDegree(deg);
			setMinutes(min);
			setFractionalMinutes(fracMin);
		}
		else
		{
			negative = true;
			int deg = (int) Math.ceil(degree);
			double remainder = degree - deg;
			double minutes = -remainder * 60;
			int min = (int) Math.floor(minutes);
			int fracMin = (int) Math.floor((minutes - min) * 1000);
			setDegree(deg);
			setMinutes(min);
			setFractionalMinutes(fracMin);
		}
	}
	
	public boolean isNegative()
	{
		return negative;
	}

	public void setNegative(boolean negative)
	{
		this.negative = negative;
	}
	public String toString()
	{
		return Math.abs(degree) + "\u00B0 " + getMinuesWithFractions() + "\'";
	}

	private String pad(int number, int desiredSize)
	{
		String res = Integer.toString(number);
		while (res.length() < desiredSize)
		{
			res = "0" + res;
		}
		return res;
	}

	public String getMinuesWithFractions()
	{
		return pad(minutes, 2) + "." + pad(fractionalMinutes, 3);
	}

	/**
	 * @return the degree
	 */
	public int getDegree()
	{
		return degree;
	}

	/**
	 * @param degree
	 *            the degree to set
	 */
	public void setDegree(int degree)
	{
		if (Math.abs(degree) > 180)
		{
			throw new IllegalArgumentException("Degrees must be in range [-180 to 180]");
		}
		this.degree = degree;
	}

	/**
	 * @return the minutes
	 */
	public int getMinutes()
	{
		return minutes;
	}

	/**
	 * @param minutes
	 *            the minutes to set
	 */
	public void setMinutes(int minutes)
	{
		if (minutes > 59 || minutes < 0)
		{
			throw new IllegalArgumentException("Minutes must be in range [0 to 59]");
		}
		this.minutes = minutes;
	}

	/**
	 * @return the fractionalMinutes
	 */
	public int getFractionalMinutes()
	{
		return fractionalMinutes;
	}

	/**
	 * @param fractionalMinutes
	 *            the fractionalMinutes to set
	 */
	public void setFractionalMinutes(int fractionalMinutes)
	{
		if (fractionalMinutes > 999 || fractionalMinutes < 0)
		{
			throw new IllegalArgumentException("Minutes must be in range [0 to 999]");
		}
		this.fractionalMinutes = fractionalMinutes;
	}

	/**
	 * Get the coordinate formatted as a positive/negative fractional degree
	 * value
	 * 
	 * @return
	 */
	public String toDecimalString()
	{
		return Double.toString(getDegreeWithFraction());
	}

	public double getDegreeWithFraction()
	{
		double angle = getDegree();
		if (isNegative())
		{
			angle -= getMinutes() / 60.0;
			angle -= getFractionalMinutes() / 60000.0;
		}
		else
		{
			angle += getMinutes() / 60.0;
			angle += getFractionalMinutes() / 60000.0;

		}
		return angle;
	}

	@Override
	public boolean equals(Object obj)
	{
		if(obj instanceof Coordinate)
		{
			Coordinate other = (Coordinate)obj;
			if(other.negative == negative 
					&& other.degree == degree 
					&& other.minutes == minutes 
					&& other.fractionalMinutes == fractionalMinutes)
			{
				return true;
			}
		}
		return false;
	}

}
